package cn.stylefeng.roses.kernel.pay.modular.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import cn.stylefeng.roses.kernel.auth.api.context.LoginContext;
import cn.stylefeng.roses.kernel.db.api.factory.PageFactory;
import cn.stylefeng.roses.kernel.db.api.factory.PageResultFactory;
import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult;
import cn.stylefeng.roses.kernel.pay.api.enums.OrderStateEnum;
import cn.stylefeng.roses.kernel.pay.api.exception.PayException;
import cn.stylefeng.roses.kernel.pay.api.exception.enums.OrderExceptionEnum;
import cn.stylefeng.roses.kernel.pay.api.pojo.YunGouOSConfig;
import cn.stylefeng.roses.kernel.pay.modular.entity.Goods;
import cn.stylefeng.roses.kernel.pay.modular.entity.Order;
import cn.stylefeng.roses.kernel.pay.modular.factory.OrderFactory;
import cn.stylefeng.roses.kernel.pay.modular.mapper.OrderMapper;
import cn.stylefeng.roses.kernel.pay.modular.pojo.GoodsRequest;
import cn.stylefeng.roses.kernel.pay.modular.pojo.NotifyRequest;
import cn.stylefeng.roses.kernel.pay.modular.pojo.OrderDTO;
import cn.stylefeng.roses.kernel.pay.modular.pojo.OrderRequest;
import cn.stylefeng.roses.kernel.pay.modular.service.GoodsService;
import cn.stylefeng.roses.kernel.pay.modular.service.OrderService;
import cn.stylefeng.roses.kernel.pay.sdk.YunGouSignUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;


/**
 * 订单业务实现层
 *
 * @author stylefeng
 * @date 2021/06/23 22:28
 */
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {

    private static final Object LOCK = new Object();

    @Resource
    private GoodsService goodsService;

    @Resource
    private YunGouOSConfig yunGouOSConfig;

    @Override
    public Order createOrder(OrderRequest orderRequest) {

        // 查询商品是否存在
        GoodsRequest goodsRequest = new GoodsRequest();
        goodsRequest.setGoodsId(orderRequest.getGoodsId());
        Goods detail = goodsService.detail(goodsRequest);

        // 查询用户是否有未支付订单
        validateUserHaveByGoods();

        // 计算过期时间，如果永久有效，则过期时间为null
        Date expirationTime = null;
        if (!detail.getExpiryMonth().equals(-1)) {
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.MONTH, detail.getExpiryMonth());
            expirationTime = calendar.getTime();
        }

        // 创建订单对象
        Order order = OrderFactory.createOrder(detail, expirationTime);
        this.save(order);
        return order;
    }

    @Override
    public String pay(OrderRequest orderRequest) {

        // 查询订单信息
        Order orderInfo = this.detail(orderRequest);

        // 获取支付二维码
        Map<String, String> params = new HashMap<>();

        // Guns系统订单号，日期+随机字符串
        params.put("out_trade_no", orderInfo.getOrderNumber());

        // 支付金额
        params.put("total_fee", String.valueOf(orderInfo.getPayPrice()));

        // 聚合支付商户号
        params.put("mch_id", yunGouOSConfig.getMergePayShopId());

        // 商品描述
        params.put("body", orderInfo.getGoodsName());

        // 返回类型，（1、返回原生的支付连接需要自行生成二维码；2、直接返回付款二维码地址，页面上展示即可。）
        params.put("type", "2");

        // 附加数据，回调时候原路返回
        params.put("attach", "");

        // 异步回调地址，用户支付成功后系统将会把支付结果发送到该地址，不填则无回调
        params.put("notify_url", yunGouOSConfig.getMergePayNotifyUrl());

        // 同步地址，支付完毕后用户浏览器返回到该地址，如果不传递，页面支付完成后页面自动关闭，强烈建议传递。url不可包含？号、不可携带参数
        params.put("return_url", yunGouOSConfig.getMergePayReturnUrl());

        // 生成签名
        Map<String, String> signParams = new HashMap<>();
        signParams.put("out_trade_no", params.get("out_trade_no"));
        signParams.put("total_fee", params.get("total_fee"));
        signParams.put("mch_id", params.get("mch_id"));
        signParams.put("body", params.get("body"));
        String sign = YunGouSignUtil.createSign(signParams, yunGouOSConfig.getMergePaySecret());
        params.put("sign", sign);

        HashMap<String, Object> newMap = new HashMap<>(params);
        String jsonResult = HttpUtil.post(yunGouOSConfig.getMergePayUrl(), newMap);

        // 获取响应结果
        JSONObject jsonObject = JSON.parseObject(jsonResult);
        Integer code = jsonObject.getInteger("code");

        // 0成功，1失败
        if (code != null && code.equals(0)) {
            return jsonObject.getString("data");
        } else {
            throw new PayException(OrderExceptionEnum.ORDER_PAY_ERROR);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void payNotify(NotifyRequest notifyRequest) {
        System.out.println("接收到支付回调：" + JSON.toJSONString(notifyRequest));

        // 参数不能为空
        if (notifyRequest == null) {
            throw new PayException(OrderExceptionEnum.ORDER_NOTIFY_ERROR, "参数为空");
        }

        // 0是支付商响应失败，直接返回
        if ("0".equals(notifyRequest.getCode())) {
            return;
        }

        // 校验支付渠道
        if (!"alipay".equals(notifyRequest.getPayChannel()) && !"wxpay".equals(notifyRequest.getPayChannel())) {
            throw new PayException(OrderExceptionEnum.ORDER_NOTIFY_ERROR, "支付渠道错误");
        }

        // 校验商户号
        if (!notifyRequest.getMchId().equals(yunGouOSConfig.getWxShopId()) && !notifyRequest.getMchId().equals(yunGouOSConfig.getAliShopId())) {
            throw new PayException(OrderExceptionEnum.ORDER_NOTIFY_ERROR, "商户号错误");
        }

        // 判断签名是否一致
        Map<String, String> signParams = new HashMap<>();
        signParams.put("code", notifyRequest.getCode());
        signParams.put("orderNo", notifyRequest.getOrderNo());
        signParams.put("outTradeNo", notifyRequest.getOutTradeNo());
        signParams.put("payNo", notifyRequest.getPayNo());
        signParams.put("money", notifyRequest.getMoney());
        signParams.put("mchId", notifyRequest.getMchId());
        String sign = YunGouSignUtil.createSign(signParams, notifyRequest.getMchId().equals(yunGouOSConfig.getWxShopId()) ? yunGouOSConfig.getPayWxKey() : yunGouOSConfig.getPayAliKey());
        if (!sign.equals(notifyRequest.getSign())) {
            throw new PayException(OrderExceptionEnum.ORDER_NOTIFY_ERROR, "签名不一致");
        }

        synchronized (LOCK) {
            // 查询订单
            LambdaQueryWrapper<Order> orderLambdaQueryWrapper = new LambdaQueryWrapper<>();
            orderLambdaQueryWrapper.eq(Order::getOrderNumber, notifyRequest.getOutTradeNo());
            Order order = this.getOne(orderLambdaQueryWrapper, false);
            if (order == null) {
                throw new PayException(OrderExceptionEnum.ORDER_NOTIFY_ERROR, "订单不存在");
            }

            // 判断定时是否处理过
            if (OrderStateEnum.COMPLETE.getCode().equals(order.getState())) {
                return;
            }

            // 设置订单状态为成功
            order.setState(OrderStateEnum.COMPLETE.getCode());

            // 设置支付时间
            try {
                order.setPayTime(DateUtil.parse(notifyRequest.getTime(), "yyyyMMddHHmmss"));
            } catch (Exception e) {
                order.setPayTime(new Date());
            }

            // YunGouOS系统单号
            order.setOrderNo(notifyRequest.getOrderNo());

            // 支付单号
            order.setPayNo(notifyRequest.getPayNo());

            // 支付渠道
            order.setPayChannel(notifyRequest.getPayChannel());

            // 用户openId
            order.setOpenId(notifyRequest.getOpenId());

            // 更新订单状态
            this.updateById(order);
        }
    }

    @Override
    public List<OrderDTO> findList(OrderRequest orderRequest) {

        Long userId = LoginContext.me().getLoginUser().getUserId();
        orderRequest.setCustomerId(userId);

        LambdaQueryWrapper<Order> wrapper = this.createWrapper(orderRequest);
        List<Order> list = this.list(wrapper);

        // 转为DTO
        List<OrderDTO> orderDTOS = new ArrayList<>();
        for (Order order : list) {
            OrderDTO orderDTO = new OrderDTO();
            BeanUtil.copyProperties(order, orderDTO);
            orderDTOS.add(orderDTO);
        }

        return orderDTOS;
    }

    @Override
    public void add(OrderRequest orderRequest) {
        Order order = new Order();
        BeanUtil.copyProperties(orderRequest, order);
        this.save(order);
    }

    @Override
    public void del(OrderRequest orderRequest) {
        Order order = this.queryOrder(orderRequest);
        this.removeById(order.getOrderId());
    }

    @Override
    public void edit(OrderRequest orderRequest) {
        Order order = this.queryOrder(orderRequest);
        BeanUtil.copyProperties(orderRequest, order);
        this.updateById(order);
    }

    @Override
    public Order detail(OrderRequest orderRequest) {
        return this.queryOrder(orderRequest);
    }

    @Override
    public PageResult<Order> findPage(OrderRequest orderRequest) {
        LambdaQueryWrapper<Order> wrapper = createWrapper(orderRequest);
        Page<Order> sysRolePage = this.page(PageFactory.defaultPage(), wrapper);
        return PageResultFactory.createPageResult(sysRolePage);
    }

    @Override
    public void cancelOrder(OrderRequest orderRequest) {

        Order order = validateSelfOrder(orderRequest);

        order.setState(OrderStateEnum.CANCEL.getCode());

        this.updateById(order);
    }

    @Override
    public void deleteOrder(OrderRequest orderRequest) {

        Order order = validateSelfOrder(orderRequest);

        // 必须是已取消和已经退款的订单可以删除
        if (OrderStateEnum.CANCEL.getCode().equals(order.getState()) || OrderStateEnum.BACK_MONEY.getCode().equals(order.getState())) {
            this.removeById(order.getOrderId());
        } else {
            throw new PayException(OrderExceptionEnum.ORDER_DELETE_ERROR);
        }
    }

    /**
     * 获取信息
     *
     * @author stylefeng
     * @date 2021/06/23 22:28
     */
    private Order queryOrder(OrderRequest orderRequest) {
        Order order = this.getById(orderRequest.getOrderId());
        if (ObjectUtil.isEmpty(order)) {
            throw new PayException(OrderExceptionEnum.ORDER_NOT_EXISTED);
        }
        return order;
    }

    /**
     * 创建查询wrapper
     *
     * @author stylefeng
     * @date 2021/06/23 22:28
     */
    private LambdaQueryWrapper<Order> createWrapper(OrderRequest orderRequest) {
        LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<>();

        Long orderId = orderRequest.getOrderId();
        String orderNumber = orderRequest.getOrderNumber();
        Long customerId = orderRequest.getCustomerId();

        queryWrapper.eq(ObjectUtil.isNotNull(orderId), Order::getOrderId, orderId);
        queryWrapper.like(ObjectUtil.isNotEmpty(orderNumber), Order::getOrderNumber, orderNumber);
        queryWrapper.eq(ObjectUtil.isNotNull(customerId), Order::getCustomerId, customerId);

        return queryWrapper;
    }

    /**
     * 校验用户是否买过此商品
     *
     * @author fengshuonan
     * @date 2021/6/23 22:57
     */
    private void validateUserHaveByGoods() {

        // 获取当前用户
        Long userId = LoginContext.me().getLoginUser().getUserId();

        LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();

        // 查询当前用户该商品，待支付的订单
        wrapper.eq(Order::getCustomerId, userId)
                .and(i -> i.in(Order::getState, OrderStateEnum.WAIT_PAY.getCode()));

        Order haveOrder = this.getOne(wrapper, false);

        // 存在待支付订单
        if (haveOrder != null && OrderStateEnum.WAIT_PAY.getCode().equals(haveOrder.getState())) {
            throw new PayException(OrderExceptionEnum.WAIT_TO_PAY_ORDER);
        }

    }

    /**
     * 判断订单是否是自己的订单，如果不是自己的订单不能操作
     *
     * @author fengshuonan
     * @date 2021/6/29 9:32
     */
    private Order validateSelfOrder(OrderRequest orderRequest) {

        Order detail = this.detail(orderRequest);

        Long userId = LoginContext.me().getLoginUser().getUserId();

        if (!userId.equals(detail.getCustomerId())) {
            throw new PayException(OrderExceptionEnum.ORDER_OPERATE_ERROR);
        }

        return detail;
    }

}
